﻿#include "Server.h"
#include <iostream>
// #include "apue.h"
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <cstdio>

#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cassert>
#include <string>
#include <string.h>
#include <regex>
#include <errno.h>

using namespace std;

/*
    global variables
*/
enum OPTION 
{
    OPT_l,
    OPT_H,
    OPT_p,
    OPT_h,
    OPT_pt,
    OPT_NUM
};

// ftp server instance
Server *fserver;

// preserve all of the options
int fserver_options[OPT_NUM];

// ftp server port
int u_port;

// ftp server address
char* u_address;

// ftp server path
char* server_path;

bool *is_observed;

// check if the options are valid
bool is_valid = true;

/*
    declare functions
*/
void create_daemond();

// get the command of the user and preserve the options
void get_options(char *argv[], int cur, int num_args);

// preserve the path
void get_path(char *path, int cur, int num_args);

void handle_options();

void set_invalid();

bool get_port(char *argv[], int cur, int num_args);

bool get_address(char *argv[], int cur, int num_args);

void check_port_valid(char *argv[], int cur, int num_args);

void check_address_valid(char *argv[], int cur, int num_args);


int main(int argc, char *argv[])
{
    // create_daemond();
    // allocate memory for the arrays
    memset(fserver_options, 0, OPT_NUM * sizeof(int));
    is_observed = new bool[argc];
    for(int i = 0; i < argc; i++) is_observed[i] = false;

    int num_args = argc - 1;
    if(num_args == 0)
    {
        exit(1);
    }
    
    // read the command of user
    for(int i = 1; i < argc; i++)
    {
        if(!is_valid) exit(1);
        if(!is_observed[i] && argv[i][0] == '-')
        {
            get_options(argv, i, num_args);
            is_observed[i] = true;
        }
        else if(!is_observed[i])
        {
            get_path(argv[i], i, num_args);
            is_observed[i] = true;
        }
    }

    if(!is_valid) exit(1);

    // set port, address or print help
    handle_options();

    // start the ftp server
    // create socket
    if(fserver->create_socket() == -1)
    {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(1);
    }
    // init address
    fserver->init_address();
    // bind socket
    if(fserver->bind_socket() == -1)
    {
        printf("failed to start fserver: %s:%d %s\n", fserver->get_address(), fserver->get_port(), strerror(errno));
        exit(1);
    }
    // listen socket
    if(fserver->listen_socket() == -1)
    {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        exit(1);
    }
    fserver->show_listening();
    fserver->print_path_files();

    // waiting for client's request
    while(1)
    {
        struct sockaddr_in client_addr;
        socklen_t size = sizeof(client_addr);
        if(fserver->accept_client(client_addr, size) == -1)
        {
            continue;
        }
        
        while(1)
        {
            if(fserver->read_buffer() == 0) 
            {
                fserver->flush_buffer();
                break;
            }
            fserver->handle_options();
            fserver->flush_buffer();
        }
        // printf("close connfd\n");
        fserver->close_connfd();
    }
    fserver->close_listenfd();

    return 0;
}

void get_options(char *argv[], int cur, int num_args)
{
    char *cmd = argv[cur];
    // check if the cmd is null
    assert(cmd);
    // get all the options of the cmd
    if(0 == strcmp(cmd, "-h") || 0 == strcmp(cmd, "--help"))
    {
        fserver_options[OPT_h] = 1;
    }
    else if(0 == strcmp(cmd, "-H") || 0 == strcmp(cmd, "--hidden"))
    {
        fserver_options[OPT_H] = 1;
    }
    else if(0 == strcmp(cmd, "-l") || 0 == strcmp(cmd, "--listen"))
    {
        check_address_valid(argv, cur, num_args);
    }
    else if(0 == strcmp(cmd, "-p") || 0 == strcmp(cmd, "--port"))
    {
        check_port_valid(argv, cur, num_args);
    }
    else
    {
        // cmd is invalid
        set_invalid();
    }
}

void get_path(char *path, int cur, int num_args)
{
    if(cur != num_args) set_invalid();
    else
    {
        fserver_options[OPT_pt] = true;
        server_path = path;
        is_observed[cur] = true;
    }
}

void handle_options()
{
    fserver = new Server();
    if(fserver_options[OPT_h])
    {
        Server::print_help();
        exit(0);
    }
    
    if(fserver_options[OPT_H])
    {
        fserver->set_hidden(true);
    }
    else fserver->set_hidden(false);

    if(fserver_options[OPT_l])
    {
        fserver->set_address(u_address);
    }

    if(fserver_options[OPT_p])
    {
        fserver->set_port(u_port);
    }

    if(fserver_options[OPT_pt])
    {
        fserver->set_path(server_path);
    }

    // fserver->show();

}

bool get_port(char *argv[], int cur, int num_args)
{
    if(cur >= num_args) return false;
    else
    {
        int next = cur + 1;
        char *c_port = argv[next];
        assert(c_port);
        is_observed[next] = true;

        if(c_port[0] == '-')
        {
            set_invalid();
            exit(1);
        }

        // utilize regular expression to check if the port is valid
        regex regexStr("^[\\d]*$");
        smatch matchResult;

        string temp_port(c_port);
        int port;
        if(std::regex_match(temp_port, matchResult, regexStr))
        {
            port = stoi(temp_port);
        }
        else return false;

        if(port < 0 || port > 65535) return false;
        else
        {
            u_port = port;
            return true;
        }
    }
}

void check_port_valid(char *argv[], int cur, int num_args)
{
    if(get_port(argv, cur, num_args))
    {
        fserver_options[OPT_p] = 1;
    }
    else set_invalid();
}

bool get_address(char *argv[], int cur, int num_args)
{
    if(cur >= num_args) return false;
    else
    {
        int next = cur + 1;
        char *c_address = argv[next];
        assert(c_address);
        is_observed[next] = true;

        if(c_address[0] == '-')
        {
            set_invalid();
            exit(1);
        }

        // utilize regular expression to check if the address is valid
        regex regexStr("^[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}$");
        smatch matchResult;
        string temp_address(c_address);

        if(std::regex_match(temp_address, matchResult, regexStr))
        {
            u_address = c_address;
            return true;
        }
        else
        {
            printf("failed to start fserver: %s is not a valid address\n", c_address);
            exit(1);
        }
    }
}

void check_address_valid(char *argv[], int cur, int num_args)
{
    if(get_address(argv, cur, num_args))
    {
        fserver_options[OPT_l] = 1;
    }
    else set_invalid();
}

void set_invalid()
{
    is_valid = false;
    Server::print_help();
}


void create_daemond()
{
    //父进程创建子进程,然后退出,形式上脱离控制终端
    pid_t pid = fork();
    if(pid > 0)
    {
        exit(0);
    }
 
    //子进程创建一个新的会话
    setsid();
    //改变当前目录为根目录
    chdir("/");
    //设置umask掩码,防止从父进程继承的过来的屏蔽字拒绝某些权限
    umask(0);
    //关闭文件描述符,节省系统资源
    //STDIN_FILENO就是标准输入设备（一般是键盘）的文件描述符
    //写入/dev/null的东西会被系统丢掉
    close(STDIN_FILENO);
    //再一次的保护
    open("/dev/null",O_RDWR);
    dup2(0,1);
    dup2(0,2);
    //守护进程的核心逻辑
    //退出
}
